package top.cardone.data.jdbc.datasource.looku;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import top.cardone.context.ApplicationContextHolder;

import java.util.Map;

/**
 * @author yao hai tao
 */
@lombok.extern.log4j.Log4j2
public class DefaultRoutingDataSourceAspect implements RoutingDataSourceAspect {
    @lombok.Setter
    private String defaultTransationManagerName = "transationManager";

    @lombok.Setter
    private Map<String, RoutingDataSource> routingDataSourceMap = com.google.common.collect.Maps.newConcurrentMap();

    private Transactional findTransactional(ProceedingJoinPoint joinPoint) {
        final java.lang.reflect.Method method = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getMethod();

        return org.springframework.core.annotation.AnnotationUtils.findAnnotation(method, org.springframework.transaction.annotation.Transactional.class);
    }

    private RoutingDataSource getRoutingDataSource(Transactional transactional) {
        if (transactional == null) {
            return null;
        }

        String transationManagerName = StringUtils.defaultString(transactional.value(), defaultTransationManagerName);

        RoutingDataSource routingDataSource = routingDataSourceMap.get(transationManagerName);

        if (routingDataSource != null) {
            return routingDataSource;
        }

        return ApplicationContextHolder.func(DataSourceTransactionManager.class, dataSourceTransactionManager -> {
            if (!(dataSourceTransactionManager.getDataSource() instanceof RoutingDataSource)) {
                return null;
            }

            RoutingDataSource dataSource = (RoutingDataSource) dataSourceTransactionManager.getDataSource();

            routingDataSourceMap.put(transationManagerName, dataSource);

            return dataSource;
        }, transationManagerName);
    }

    @Override
    public Object postProcessBeforeSetTargetDataSource(ProceedingJoinPoint joinPoint) throws Throwable {
        RoutingDataSource routingDataSource = null;

        try {
            Transactional transactional = this.findTransactional(joinPoint);

            routingDataSource = getRoutingDataSource(transactional);

            if (routingDataSource != null) {
                routingDataSource.addKey(transactional.readOnly());
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        try {
            return joinPoint.proceed();
        } finally {
            if (routingDataSource != null) {
                routingDataSource.removeKey();
            }
        }
    }
}
